#include <QJsonObject>
#include <QJsonArray>

#include <osg/Geometry>

#include <GCE2d_MakeSegment.hxx>

#include "LineStrip2D.h"
#include "JsonReader.h"
#include "GLSLPrograms.h"
#include "OcctOsg.h"
#include "OsgUtil.h"
#include "UCS.h"
#include "LineStrip3D.h"
#include "Scene.h"

// Edge
const char *vertexShaderEdge =
R"(#version 330
layout (location = 0) in vec3 aPos;
layout (location = 2) in vec4 aColor;

out vec4 Color;

uniform mat4 osg_ModelViewMatrix;
uniform mat4 osg_ViewMatrix;
uniform mat4 osg_ModelViewProjectionMatrix;
uniform vec4 detColor;

void main()
{
    gl_Position = osg_ModelViewProjectionMatrix * vec4(aPos, 1.0);
	Color = aColor + detColor;
}
)";

const char *fragShaderEdge =
R"(#version 330
out vec4 FragColor;

in vec4 Color;

void main()
{
    FragColor = Color;
	gl_FragDepth = gl_FragCoord.z - 0.005;
}
)";

// 类似于createEdgeProgram, 除了z值处理之外
osg::ref_ptr<osg::Program> createEdgeProgram2()
{
	static osg::ref_ptr<osg::Program> program;
	if (!program)
	{
		program = new osg::Program();
		program->addShader(new osg::Shader(osg::Shader::VERTEX, vertexShaderEdge));
		program->addShader(new osg::Shader(osg::Shader::FRAGMENT, fragShaderEdge));
	}
	return program;
}

struct LineStrip2DPrivate
{
	LineStrip2DPrivate()
		: m_rep3D(new osg::Geometry())
	{
		m_rep3D->setNodeMask(D2LineMask | D2CurveMask);
		osg::ref_ptr<osg::Program> program = createEdgeProgram2();
		auto ss = m_rep3D->getOrCreateStateSet();
		ss->setAttributeAndModes(program, osg::StateAttribute::ON);
	}
	osg::ref_ptr<osg::Geometry> m_rep3D;
	std::vector <osg::Vec3d> m_points;
	osg::Vec4 m_color = { 1, 1, 1, 1 };
	bool m_selected = false;
};

LineStrip2D::LineStrip2D(INode* parent)
	: Ent2DCurve(parent)
	, d_ptr(new LineStrip2DPrivate)
{
}

void LineStrip2D::setPoints(const std::vector<osg::Vec3d> &points)
{
	if (d_ptr->m_points != points)
	{
		d_ptr->m_points = points;
		addFlags(UPDATE_GEOMETRY);
	}
}

void LineStrip2D::setColor(const osg::Vec4 &color)
{
	if (d_ptr->m_color != color)
	{
		d_ptr->m_color = color;
		addFlags(UPDATE_GEOMETRY);
	}
}

const std::vector <osg::Vec3d>& LineStrip2D::getPoints() const
{
	return d_ptr->m_points;
}

const osg::Vec4& LineStrip2D::color() const
{
	return d_ptr->m_color;
}

void LineStrip2D::update3D()
{
	if (flags())
	{
		d_ptr->m_rep3D->getPrimitiveSetList().clear();
		bool draw = d_ptr->m_points.size() >= 2;
		if (d_ptr->m_points.size() == 2)
		{
			if (d_ptr->m_points[0] == d_ptr->m_points[1])
				draw = false;
		}
		if (draw)
		{
			auto va = new osg::Vec3Array();
			for (auto pt : d_ptr->m_points)
			{
				va->push_back(pt);
			}

			osg::ref_ptr<osg::Vec4Array> ca = new osg::Vec4Array();

			const osg::Vec4f& selectedColor = osg::Vec4f(1.f, 99.f / 255, 71.f / 255, 1.f);
			if (d_ptr->m_selected)
				ca->push_back(selectedColor);
			else
				ca->push_back(d_ptr->m_color);

			d_ptr->m_rep3D->setVertexArray(va);
			d_ptr->m_rep3D->setColorArray(ca.get(), osg::Array::BIND_OVERALL);
			d_ptr->m_rep3D->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::LINE_STRIP, 0, d_ptr->m_points.size()));
		}
		else
		{
			d_ptr->m_rep3D->setVertexArray(nullptr);
			d_ptr->m_rep3D->setColorArray(nullptr, osg::Array::BIND_OVERALL);
			d_ptr->m_rep3D->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::LINES, 0, 0));
		}
		d_ptr->m_rep3D->dirtyGLObjects();
		removeFlags(0xFFFFFFFF);
	}
}

osg::Node * LineStrip2D::getRep3D() const
{
	return d_ptr->m_rep3D.get();
}

void LineStrip2D::setSelected(bool val)
{
	if (d_ptr->m_selected != val)
	{
		d_ptr->m_selected = val;
		addFlags(UPDATE_GEOMETRY);
	}
}

void LineStrip2D::save(QJsonObject & json) const
{
	Ent2DCurve::save(json);

	QJsonArray points;
	for (auto pt : d_ptr->m_points)
	{
		points.append(saveVec2d({ pt.x() ,pt.y() }));
	}
	json["points"] = points;

	json["color"] = saveVec4f(d_ptr->m_color);
}

void LineStrip2D::load(const QJsonObject& json, JsonReader& reader)
{
	Ent2DCurve::load(json, reader);

	QJsonArray pointsJson = json["points"].toArray();
	for (int i = 0; i < pointsJson.count(); i++)
	{
		QJsonValue ptJson = pointsJson.at(i);
		osg::Vec3d pt = { loadVec2d(ptJson.toArray()),0 };
		d_ptr->m_points.push_back(pt);
	}
	d_ptr->m_color = loadVec4f(json["color"].toArray());
}

std::vector<osg::Vec3d> LineStrip2D::handles() const
{
	if (d_ptr->m_points.size() == 2)
	{
		std::vector<osg::Vec3d> result;
		result.push_back(d_ptr->m_points[0]);
		result.push_back((d_ptr->m_points[0] + d_ptr->m_points[1]) * 0.5);
		result.push_back(d_ptr->m_points[1]);
		return result;
	}
	else
	{
		return d_ptr->m_points;
	}
}

void LineStrip2D::move(const osg::Vec3d& offset)
{
	for (int i = 0; i< d_ptr->m_points.size();i++)
	{
		d_ptr->m_points[i] = d_ptr->m_points[i] + offset;
	}
	addFlags(UPDATE_GEOMETRY);
}

void LineStrip2D::scale(const osg::Vec3d & center, double scale)
{
	for (auto& pt : d_ptr->m_points) 
	{
		pt = center + (pt - center) * scale;
	}
}

void LineStrip2D::mirror(const osg::Vec3d& start, const osg::Vec3d& end)
{
	auto points = getPoints();
	std::vector<osg::Vec3d> mirrors;
	for (auto point: points)
	{
		auto mirror = mirror2D(start,end,point);
		mirrors.push_back(mirror);
	}
	setPoints(mirrors);
	addFlags(UPDATE_GEOMETRY);
}

void LineStrip2D::stretch(int index, const osg::Vec3d &offset)
{
	if (d_ptr->m_points.size() == 2)
	{
		switch (index)
		{
		case 0:
			d_ptr->m_points[0] = d_ptr->m_points[0] + offset;
			break;
		case 1:
			d_ptr->m_points[0] = d_ptr->m_points[0] + offset;
			d_ptr->m_points[1] = d_ptr->m_points[1] + offset;
			break;
		case 2:
			d_ptr->m_points[1] = d_ptr->m_points[1] + offset;
			break;
		}
	}
	else
		d_ptr->m_points[index] = d_ptr->m_points[index] + offset;
	addFlags(UPDATE_GEOMETRY);
}

std::shared_ptr<Ent> LineStrip2D::clone() const
{
	auto line = std::make_shared<LineStrip2D>(getParent());
	line->copy(*this);
	return line;
}

bool LineStrip2D::explode(std::vector<std::shared_ptr<Ent>>& result) const
{
	if (d_ptr->m_points.size() > 2)
	{
		for (int i = 1; i < d_ptr->m_points.size(); i++)
		{
			std::shared_ptr<LineStrip2D> line = std::make_shared<LineStrip2D>(getParent());
			std::vector <osg::Vec3d> points;
			points.push_back(d_ptr->m_points[i - 1]);
			points.push_back(d_ptr->m_points[i]);
			line->setPoints(points);
			line->setColor(d_ptr->m_color);
			result.push_back(line);
		}
		return true;
	}
	else
	{
		return false;
	}
}

void LineStrip2D::copy(const Ent& ent)
{
	auto line = dynamic_cast<const LineStrip2D*>(&ent);
	if (line)
	{
		d_ptr->m_color = line->color();
		d_ptr->m_points = line->getPoints();
		addFlags(UPDATE_GEOMETRY);
	}
}

void LineStrip2D::rotate(const double angle, const osg::Vec2d & basePoint)
{
	for (int i = 0; i < d_ptr->m_points.size(); i++)
	{
		rotate2D(basePoint.x(), basePoint.y(), angle, d_ptr->m_points[i]);
	}
	addFlags(UPDATE_GEOMETRY);
}

std::vector<Handle(Geom2d_Curve)> LineStrip2D::getCurve2d() const
{
	std::vector<Handle(Geom2d_Curve)> result;
	if (d_ptr->m_points.size() >= 2)
	{
		for (int i = 1; i < d_ptr->m_points.size(); i++)
		{
			GCE2d_MakeSegment maker(toPnt2d(d_ptr->m_points[i - 1]), toPnt2d(d_ptr->m_points[i]));
			if (maker.IsDone())
			{
				result.push_back(maker.Value());
			}
		}
	}
	return result;
}

std::shared_ptr<Ent2DCurve> LineStrip2D::extend(bool front, double param, const gp_Pnt2d& pt) const
{
	auto cloneEnt = std::static_pointer_cast<LineStrip2D>(clone());
	if (d_ptr->m_points.size() >= 2)
	{
		if (front)
		{
			cloneEnt->d_ptr->m_points.front() = osg::Vec3d(pt.X(), pt.Y(), 0);
		}
		else
		{
			cloneEnt->d_ptr->m_points.back() = osg::Vec3d(pt.X(), pt.Y(), 0);
		}
	}
	return cloneEnt;
}

std::vector<std::shared_ptr<Ent2DCurve>> LineStrip2D::trim(const SectionParam& from, const SectionParam& to) const
{
	std::vector<std::shared_ptr<Ent2DCurve>> result;
	auto curve2ds = getCurve2d();
	SectionParam start, end;
	getStartEndParam(curve2ds, start, end);
	if (start < from)
	{
		auto cloneEnt = std::static_pointer_cast<LineStrip2D>(clone());
		cloneEnt->d_ptr->m_points.resize(from.sectionNo + 1);
		auto fp = curve2ds[from.sectionNo]->FirstParameter();
		if (from.param > fp)
		{
			auto pt = curve2ds[from.sectionNo]->Value(from.param);
			cloneEnt->d_ptr->m_points.push_back(osg::Vec3d(pt.X(), pt.Y(), 0));
		}
		result.push_back(cloneEnt);
	}
	if (to < end)
	{
		auto cloneEnt = std::static_pointer_cast<LineStrip2D>(clone());
		cloneEnt->d_ptr->m_points.erase(cloneEnt->d_ptr->m_points.begin(), cloneEnt->d_ptr->m_points.begin() + to.sectionNo + 1);
		auto lp = curve2ds[to.sectionNo]->LastParameter();
		if (to.param < lp)
		{
			auto pt = curve2ds[to.sectionNo]->Value(to.param);
			cloneEnt->d_ptr->m_points.insert(cloneEnt->d_ptr->m_points.begin(), osg::Vec3d(pt.X(), pt.Y(), 0));
		}
		result.push_back(cloneEnt);
	}
	return result;
}

bool LineStrip2D::offset(double offset)
{
	if (d_ptr->m_points.size() == 2)
	{
		osg::Vec3d startPt = d_ptr->m_points[0];
		osg::Vec3d endPt = d_ptr->m_points[1];
		double dx = endPt.x() - startPt.x();
		double dy = endPt.y() - startPt.y();
		double len = sqrt(dx*dx + dy * dy);
		double pp = offset / len;
		double offsetX = (-dy) * pp;
		double offsetY = dx * pp;
		d_ptr->m_points[0] = osg::Vec3d(startPt.x() + offsetX, startPt.y() + offsetY, 0);
		d_ptr->m_points[1] = osg::Vec3d(endPt.x() + offsetX, endPt.y() + offsetY, 0);
		addFlags(UPDATE_GEOMETRY);
		return true;
	}
	return false;
}

bool LineStrip2D::offset(double offset, const osg::Vec3d & offsetSide)
{
	if (d_ptr->m_points.size() == 2)
	{
		osg::Vec3d startPt = d_ptr->m_points[0];
		osg::Vec3d endPt = d_ptr->m_points[1];
		double dx = endPt.x() - startPt.x();
		double dy = endPt.y() - startPt.y();
		double detx = offsetSide.x() - startPt.x();
		double dety = offsetSide.y() - startPt.y();
		int sign;
		double det = dx * dety - detx * dy;
		if (det > 0.0)
			sign = 1;
		else if (det < 0.0)
			sign = -1;
		else
			return false;
		double len = sqrt(dx*dx + dy * dy);
		double pp = (double)sign * offset / len;
		double offsetX = (-dy) * pp;
		double offsetY = dx * pp;
		d_ptr->m_points[0] = osg::Vec3d(startPt.x() + offsetX, startPt.y() + offsetY, 0);
		d_ptr->m_points[1] = osg::Vec3d(endPt.x() + offsetX, endPt.y() + offsetY, 0);
		addFlags(UPDATE_GEOMETRY);
		return true;
	}
	return false;
}

std::vector<std::shared_ptr<Ent3DCurve>> LineStrip2D::to3d()
{
	std::vector<std::shared_ptr<Ent3DCurve>> result;
	UCS* ucs = static_cast<UCS*>(getParent());
	auto matrix = ucs->getMatrix();
	std::vector <osg::Vec3d> points;
	for (auto pt : d_ptr->m_points)
	{
		osg::Vec3d point = osg::Vec3d(pt.x(), pt.y(), 0);
		point = point * matrix;
		points.push_back(point);
	}
	Scene* scene = static_cast<Scene*>(getRoot());
	auto line = std::make_shared<LineStrip3D>(scene);
	line->setPoints(points);
	result.push_back(line);
	return result;
}
